/*
	The MIT License

	Copyright (c) 2010 IFMO/GameDev Studio

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.
*/

#include "ge_local.h"
#include <algorithm>

/*-----------------------------------------------------------------------------
	TriMesh :
-----------------------------------------------------------------------------*/

//
//	ERendMesh::ERendMesh
//
ETriMesh::ETriMesh( uint format )
{
	this->format = format;
	root = NULL;
}


//
//	ETriMesh::~ETriMesh
//
ETriMesh::~ETriMesh( void )
{
	CleanupOctree(root);
}


//
//	ETriMesh::SetVertexNum
//
void ETriMesh::SetVertexNum( uint num )
{
	vertices.resize(num);
}


//
//	ETriMesh::GetVertexNum
//
uint ETriMesh::GetVertexNum( void ) const
{
	return vertices.size();
}


//
//	ETriMesh::AddVertex
//
uint ETriMesh::AddVertex( const EVertex &v )
{
	vertices.push_back(v);
	return GetVertexNum()-1;
}


//
//	ETriMesh::SetVertex
//
void ETriMesh::SetVertex( uint index, const EVertex &v )
{
	ASSERT( index < vertices.size() );
	vertices[ index ] = v;
}


//
//	ETriMesh::GetVertex
//
EVertex ETriMesh::GetVertex( uint index ) const
{
	ASSERT( index < vertices.size() );
	return vertices[ index ];
}


//
//	ETriMesh::AddUniqueVertex
//
uint ETriMesh::AddUniqueVertex( const EVertex &v, float tolerance )
{
	for (uint i=0; i<vertices.size(); i++) {
		if (CompareVertices(vertices[i], v, tolerance)) {
			return i;
		}
	}

	uint num = GetVertexNum();
	
	SetVertexNum( num + 1 );	
	SetVertex( num, v );
	
	return num;
}



//
//	ETriMesh::SetTriangleNum
//
void ETriMesh::SetTriangleNum( uint num )
{
	triangles.resize(num);
}


//
//	ETriMesh::GetTriangleNum
//
uint ETriMesh::GetTriangleNum( void ) const
{
	return triangles.size();
}


//
//	ETriMesh::AddTriangle
//
uint ETriMesh::AddTriangle( uint i0, uint i1, uint i2 )
{
	triangle_s triangle;
	triangle.i0 = i0;
	triangle.i1 = i1;
	triangle.i2 = i2;
	triangles.push_back( triangle );
	
	return triangles.size() - 1;
}

uint ETriMesh::AddTriangle( const EVertex& v0, const EVertex& v1, const EVertex & v2 )
{
	uint n0, n1, n2;
	n0 = AddVertex(v0);
	n1 = AddVertex(v1);
	n2 = AddVertex(v2);
	return AddTriangle(n0, n1, n2);
}

//
//	ETriMesh::SetTriangle
//
void ETriMesh::SetTriangle( uint index, uint i0, uint i1, uint i2 )
{
	triangle_s triangle;
	triangle.i0 = i0;
	triangle.i1 = i1;
	triangle.i2 = i2;
	triangles[ index ] = triangle;
}


//
//	ETriMesh::GetTriangle
//
void ETriMesh::GetTriangle( uint index, uint &i0, uint &i1, uint &i2 ) const
{
	triangle_s triangle;
	triangle	=	triangles[ index ];
	i0 = triangle.i0;
	i1 = triangle.i1;
	i2 = triangle.i2;
}


//
//	ETriMesh::AttachSG
//
void ETriMesh::AttachSG( const EShadingGroup &sg )
{
	if (sg.num)	{
		shading_groups.push_back( sg );
	}
}


//
//	ETriMesh::DetachSG
//
void ETriMesh::DetachSG( uint index )
{
	ASSERT( index < shading_groups.size() );
	std::vector<EShadingGroup>::iterator	vi;
	vi = shading_groups.begin() + index;
}


//
//	ETriMesh::GetSGNum
//
uint ETriMesh::GetSGNum( void ) const
{
	return shading_groups.size();
}


//
//
//
void ETriMesh::SetSGNum( uint num )
{
	shading_groups.resize( num );
}


//
//	ETriMesh::GetSG
//
EShadingGroup ETriMesh::GetSG( uint index ) const 
{
	ASSERT( index < shading_groups.size() );
	return shading_groups[index];
}

//
//	ETriMesh::SetSG
//
void ETriMesh::SetSG( uint index, const EShadingGroup &sg )
{
	ASSERT( index < shading_groups.size() );
	shading_groups[index] = sg;
}


//
//	ETriMesh::SetSGToAllTris
//
void ETriMesh::SetSGToAllTris( const EString &name )
{
	shading_groups.clear();
	EShadingGroup sg;
	sg.start	=	0;
	sg.num		=	GetTriangleNum();
	sg.shader	=	name;
	shading_groups.push_back( sg );
}


//
//	ETriMesh::Clone
//
IPxTriMesh ETriMesh::Clone( void ) const
{
	ETriMesh	*new_mesh = new ETriMesh(format);
	
	new_mesh->vertices			=	vertices;
	new_mesh->triangles			=	triangles;
	new_mesh->shading_groups	=	shading_groups;
	new_mesh->joints			=	joints; // ???
	
	return new_mesh;
}


//
//	ETriMesh::Merge
//
void ETriMesh::Merge( IPxTriMesh mesh )
{
	if (mesh->IsEmpty())return;
	uint v_num = vertices.size();
	uint t_num = triangles.size();
	uint sg_num = shading_groups.size();
	ETriMesh* m = mesh.to<ETriMesh>();

	vertices.insert(vertices.end(), m->vertices.begin(), m->vertices.end());
	triangles.insert(triangles.end(), m->triangles.begin(), m->triangles.end());
	shading_groups.insert(shading_groups.end(), m->shading_groups.begin(), m->shading_groups.end());

	for(uint i = t_num; i < triangles.size(); i++){
		triangles[i].i0 += v_num;
		triangles[i].i1 += v_num;
		triangles[i].i2 += v_num;
	}
	for (uint i = sg_num; i < shading_groups.size(); i++){
		shading_groups[i].start += t_num;
	}
}


//
//	ETriMesh::IsEmpty
//
bool ETriMesh::IsEmpty( void ) const 
{
	return triangles.empty() | vertices.empty();
}



/*-----------------------------------------------------------------------------
	Sophistic stuff :
-----------------------------------------------------------------------------*/

//
//	ETriMesh::Transform
//
void ETriMesh::Transform( const EMatrix &T )
{
	EMatrix	T_it = T.Inverse().Transpose();

	for (uint i=0; i<vertices.size(); i++) {
		vertices[i].position	=	vertices[i].position.Transform	( T );
		vertices[i].normal		=	vertices[i].normal.Transform	( T_it );
		vertices[i].tangent		=	vertices[i].tangent.Transform	( T_it );
		vertices[i].binormal	=	vertices[i].binormal.Transform	( T_it );
	}
}


/*-----------------------------------------------------------------------------
	SG optimizing :
-----------------------------------------------------------------------------*/

//
//	ETriMesh::GlueSGs
//
void ETriMesh::GlueSGs( void )
{
	if (shading_groups.empty()) {
		return;
	}

	//	
	//	Sort SGs :
	//
	class SortSGsByName {
	public:
		bool operator()	( const EShadingGroup sg0, const EShadingGroup sg1 ) 
		{
			if (strcmp(sg0.shader.CStr(), sg1.shader.CStr())>0) {
				return true;
			} else {
				return false;
			}
		}
	};
	
	class SortSGsByStart {
	public:
		bool operator()	( const EShadingGroup sg0, const EShadingGroup sg1 ) 
		{
			return (sg0.start <= sg1.start);
		}
	};
	
	SortSGsByName	sort_sgs_by_name;
	SortSGsByStart	sort_sgs_by_start;
	
	std::vector<EShadingGroup>::iterator	begin, end;
	begin	=	shading_groups.begin();
	end		=	shading_groups.end();
	
	//	TODO DHG : is it better to use 'stable_sort' for SGs sorting?
	sort(begin, end, sort_sgs_by_name);
	sort(begin, end, sort_sgs_by_start);
	

	//
	//	Glue SGs  :
	//	for each SG it finds all consequently following SGs, that have 
	//	the same shader, sets theirs length to zero, and expand current SG
	//
	EShadingGroup	*sg0 = &shading_groups[0];

	for (uint i=1; i<GetSGNum(); i++) {
		EShadingGroup	*sg1 = &shading_groups[i];

		if (sg0->shader==sg1->shader) {
			sg0->num += sg1->num;
			sg1->num = 0;
		} else {
			sg0 = sg1;
		}
	}

	//
	//	Remove empty SGs :	
	//	thats left after gluing :
	//
	std::vector<EShadingGroup>	old_sgs = shading_groups;
	
	shading_groups.clear();

	for (uint i=0; i<old_sgs.size(); i++) {
		if (old_sgs[i].num!=0) {
			shading_groups.push_back( old_sgs[i] );
		}
	}
}


/*-----------------------------------------------------------------------------
	Vertex merging :
-----------------------------------------------------------------------------*/

//
//	ETriMesh::MergeVertices
//
void ETriMesh::MergeVertices( void )
{
	if (GetTriangleNum()==0) {
		return;
	}

	std::vector<EVertex>	temp_vertices = vertices;
	vertices.clear();
	
	for (uint i=0; i<GetTriangleNum(); i++) {
		
		uint i0, i1, i2;
		
		GetTriangle(i, i0, i1, i2);
		
		i0 = InsertVertex( temp_vertices[i0], 0.00000, root );
		i1 = InsertVertex( temp_vertices[i1], 0.00000, root );
		i2 = InsertVertex( temp_vertices[i2], 0.00000, root );
		
		SetTriangle(i, i0, i1, i2);
	}
	
	temp_vertices.clear();
	CleanupOctree(root);
	root = NULL;
}


//
//	ETriMesh::CompareVertices
//
bool ETriMesh::CompareVertices( const EVertex &v0, const EVertex &v1, float tolerance ) const
{
	if (format & GE_MESH_POSITION		)	if ( !EMath::Compare	( v0.position	,	v1.position	,	tolerance) ) return false;
	if (format & GE_MESH_NORMAL			)	if ( !EMath::Compare	( v0.normal		,	v1.normal	,	tolerance) ) return false;
	if (format & GE_MESH_COLOR_0		)	if ( !EMath::Compare	( v0.color0		,	v1.color0	,	tolerance) ) return false;
	if (format & GE_MESH_COLOR_1		)	if ( !EMath::Compare	( v0.color1		,	v1.color1	,	tolerance) ) return false;
	if (format & GE_MESH_UV_0			)	if ( !EMath::Compare	( v0.uv0		,	v1.uv0		,	tolerance) ) return false;
	if (format & GE_MESH_UV_1			)	if ( !EMath::Compare	( v0.uv1		,	v1.uv1		,	tolerance) ) return false;
	if (format & GE_MESH_TANGENT		)	if ( !EMath::Compare	( v0.tangent	,	v1.tangent	,	tolerance) ) return false;
	if (format & GE_MESH_BINORMAL		)	if ( !EMath::Compare	( v0.binormal	,	v1.binormal	,	tolerance) ) return false;
	
	//	compare joint weights :
	if ( format & GE_MESH_JOINT_WEIGHTS	) {
		EColor jv0 = EColor(&v0.joint_weights[0]);
		EColor jv1 = EColor(&v1.joint_weights[0]);
		
		if (!EMath::Compare( jv0, jv1,	tolerance)) {
			return false;
		}
	}

	//	compare joint indices :	
	if ( format & GE_MESH_JOINT_IND ) {
		if ( v0.joint_inds[0] != v1.joint_inds[0] ||
			 v0.joint_inds[1] != v1.joint_inds[1] ||
			 v0.joint_inds[2] != v1.joint_inds[2] ||
			 v0.joint_inds[3] != v1.joint_inds[3] ) 
		{
			return false;
		}
		
	}
	
	return true;
}


//
//	ETriMesh::ClassifyVertices
//	returns octree subnode index
//
uint ETriMesh::ClassifyVertices( const EVertex &v0, const EVertex &v1 )
{
	uint res = 0;
	if (v0.position.x < v1.position.x) res |= 0x1;
	if (v0.position.y < v1.position.y) res |= 0x2;
	if (v0.position.z < v1.position.z) res |= 0x4;
	return res;
}


//
//	ETriMesh::InsertVertex
//
uint ETriMesh::InsertVertex( const EVertex &v, float tolerance, octree_node_s *&node )
{
	if (!node) {
		uint id = AddVertex(v);
		node = new octree_node_s();
		node->vert_index = id;
		return id;
	}
	
	if ( CompareVertices(vertices[node->vert_index], v, tolerance) ) {
		return node->vert_index;
	}
	
	uint r = ClassifyVertices(vertices[node->vert_index], v);
	
	return InsertVertex(v, tolerance, node->nodes[r]);
}


//
//	ETriMesh::CleanupOctree
//
void ETriMesh::CleanupOctree( octree_node_s *node )
{
	if (node) {
		for (uint i=0; i<8; i++) {
			CleanupOctree(node->nodes[i]);
		}
		delete node;
	}
}


/*-----------------------------------------------------------------------------
	Tangents :
-----------------------------------------------------------------------------*/

//
//	ETriMesh::ComputeNormals
//
void ETriMesh::ComputeNormals( void )
{
	SetFormat( GetFormat() | GE_MESH_NORMAL );

	for (uint i=0; i<GetVertexNum(); i++) {
		EVertex	v	=	GetVertex(i);
		v.normal	=	EVector::kZero;
	}
	
	for (uint i=0; i<GetTriangleNum(); i++) {
		uint ind[3];
		
		GetTriangle(i, ind[0], ind[1], ind[2]);
		
		EPoint	p0	=	GetVertex(ind[0]).position;
		EPoint	p1	=	GetVertex(ind[1]).position;
		EPoint	p2	=	GetVertex(ind[2]).position;
		
		EVector	v0	=	EVector( p0, p1 );
		EVector	v1	=	EVector( p0, p2 );
		EVector	n	=	EMath::Cross( v0, v1 );

		for (uint j=0; j<3; j++) {
			EVertex	v	=	GetVertex(ind[j]);
			v.normal	=	v.normal + n;
			SetVertex( ind[j], v );
		}
		
	}

	for (uint i=0; i<GetVertexNum(); i++) {
		EVertex	v	=	GetVertex(i);
		v.normal.NormalizeSelf();
		SetVertex( i, v );
	}
}


//
//	ETriMesh::ComputeTangents
//
void ETriMesh::ComputeTangents( void )
{
	SetFormat( GetFormat() | GE_MESH_TANGENT | GE_MESH_BINORMAL );
	
	for (uint i=0; i<GetTriangleNum(); i++)
	{
		uint inds[3];
		GetTriangle(i, inds[0], inds[1], inds[2]);
		
		for (uint j=0; j<3; j++)
		{
			EVertex	*vert0	=	&vertices[inds[(0+j)%3]];
			EVertex	*vert1	=	&vertices[inds[(1+j)%3]];
			EVertex	*vert2	=	&vertices[inds[(2+j)%3]];
			
			EVector	v0	= EVector( vert0->position, vert1->position );
			EVector	v1	= EVector( vert0->position, vert2->position );
			EVector	t0	= EVector( vert0->uv0, vert1->uv0 );	
			EVector	t1	= EVector( vert0->uv0, vert2->uv0 );	

			{	// X :
				float	det		= t0.x * t1.y  -  t1.x * t0.y;
				float	dett	= v0.x * t1.y  -  v1.x * t0.y;
				float	detb	= t0.x * v1.x  -  t1.x * v0.x;
				vert0->tangent.x	= dett / det;								
				vert0->binormal.x	= detb / det;								
			}
			{	// Y :
				float	det		= t0.x * t1.y  -  t1.x * t0.y;
				float	dett	= v0.y * t1.y  -  v1.y * t0.y;
				float	detb	= t0.x * v1.y  -  t1.x * v0.y;
				vert0->tangent.y	= dett / det;								
				vert0->binormal.y	= detb / det;								
			}
			{	// Z :
				float	det		= t0.x * t1.y  -  t1.x * t0.y;
				float	dett	= v0.z * t1.y  -  v1.z * t0.y;
				float	detb	= t0.x * v1.z  -  t1.x * v0.z;
				vert0->tangent.z	= dett / det;								
				vert0->binormal.z	= detb / det;								
			}

			//vert0->normal	= Vec3Cross(v1, v0);
			
			vert0->tangent.NormalizeSelf();
			vert0->binormal.NormalizeSelf();
			vert0->normal.NormalizeSelf();
			
			EVector	temp;
			temp = EMath::Cross( vert0->tangent, vert0->normal );
			vert0->tangent = EMath::Cross( vert0->normal, temp );
			
			temp = EMath::Cross( vert0->binormal, vert0->normal );
			vert0->binormal = EMath::Cross( vert0->normal, temp );
		}
	}
}


/*-----------------------------------------------------------------------------
	BBox :
-----------------------------------------------------------------------------*/

//
//	ETriMesh::ComputeBBox
//
EBBox ETriMesh::ComputeBBox( void ) const
{
	EBBox	bbox;
	
	for (uint i=0; i<vertices.size(); i++) {
		EPoint v = vertices[i].position;
		
		bbox.Expand( v );
	}
	
	return bbox;
}


/*-----------------------------------------------------------------------------
	Triangle stuff :
-----------------------------------------------------------------------------*/

void ETriMesh::GetTriangleV0V1( uint index, EVector &v0, EVector &v1, EPoint &center ) const
{
	uint i0, i1, i2;
	GetTriangle( index, i0, i1, i2 );
	
	EVertex vx0	=	GetVertex( i0 );
	EVertex vx1	=	GetVertex( i1 );
	EVertex vx2	=	GetVertex( i2 );
	
	v0			=	vx1.position - vx0.position;
	v1			=	vx2.position - vx0.position;
	center		=	EPoint( (EVector(vx0.position) + EVector(vx1.position) + EVector(vx2.position)) / 3.0f );
}


EPoint ETriMesh::TriangleCenter( uint index ) const
{
	EVector v0, v1;
	EPoint	c;
	GetTriangleV0V1( index, v0, v1, c );
	return c;
}


EVector ETriMesh::TriangleNormal( uint index ) const
{
	EVector v0, v1;
	EPoint	c;
	GetTriangleV0V1( index, v0, v1, c );
	return EMath::Cross( v0, v1 ).Normalize();
}


float ETriMesh::TriangleArea( uint index ) const
{
	EVector v0, v1;
	EPoint	c;
	GetTriangleV0V1( index, v0, v1, c );
	return 0.5 * EMath::Cross( v0, v1 ).Length();
}
